#include <EB0/EB0.h>

namespace EB0
{
namespace States
{
#pragma region Constructor / Destructor
	// Constructor
	World::World()
	{
		// Null pointers
		mWorld = 0;
		mWorldLayer = 0;
	}

	// Destructor
	World::~World()
	{
		// Null pointers
		mWorld = 0;
		mWorldLayer = 0;
	}
#pragma endregion

#pragma region Properties
	// Get Box2D world
	b2World* World::getWorld() const
	{
		return mWorld;
	}

	// Get World Layer
	Window::Layer* World::getWorldLayer() const
	{
		return mWorldLayer;
	}
#pragma endregion

#pragma region Load / Unload
	// Load
	void World::loadWorld(Window::RenderWindow* window)
	{

	}

	// Unload
	void World::unloadWorld(Window::RenderWindow* window)
	{

	}
#pragma endregion

#pragma region Create / Destroy Scene
	// Create scene
	void World::createWorldScene(Window::RenderWindow* window)
	{
		// Create layer in which to view the world
		if (window)
		{
			// Destroy previous layer
			if (mWorldLayer)
				window->destroyLayer(mWorldLayer);

			// Create new layer
			mWorldLayer = window->createLayer();
		}

		// Define the world's bounding box
		b2AABB worldAABB;
		worldAABB.lowerBound.Set(-256.0f, -256.0f);
		worldAABB.upperBound.Set(256.0f, 256.0f);

		// Define the gravity vector
		//b2Vec2 gravity(0.0f, 10.0f); // positive means heading towards the bottom of the screen
		b2Vec2 gravity(0.0f, 0.0f);

		// Allow bodies to sleep
		bool doSleep = true;

		// Create the world
		mWorld = new b2World(worldAABB, gravity, doSleep);
	}

	// Destroy scene
	void World::destroyWorldScene(Window::RenderWindow* window)
	{
		// Remove all entities
		removeAllEntities();

		// Destroy the world
		if (mWorld)
		{
			delete mWorld;
			mWorld = 0;
		}

		// Destroy layer
		if (window && mWorldLayer)
			window->destroyLayer(mWorldLayer);
	}
#pragma endregion

#pragma region Update World
	// Update World
	void World::updateWorld(Window::RenderWindow* window, float dt)
	{
		// Step each entity in the world
		static EntityMap::iterator entityIter;
		static EntitySet completedEntities;
		static bool dirtyEntityFlag = false;

		if (!mEntities.empty())
		{
			entityIter = mEntities.begin();
			completedEntities.clear(); // clear the array of called entities
			dirtyEntityFlag = false; // no need to do extra checking... yet

			while (entityIter != mEntities.end())
			{
				// Has this entity already been called?
				if (dirtyEntityFlag && completedEntities.find(entityIter->second) != completedEntities.end())
				{
					// Skip it
					entityIter++;
					continue;
				}

				// Add the entity to the already completed list
				completedEntities.insert(entityIter->second);

				// Step the entity
				entityIter->second->step((World*)this, dt);

				// Has the entity map been altered?
				if (mDirtyEntityMap)
				{
					// Go to the beginning of the list if there are still entities
					if (!mEntities.empty())
						entityIter = mEntities.begin();

					// Reset the flag as the situation is now being taken care of
					mDirtyEntityMap = false;
					dirtyEntityFlag = true; // we'll need to perform additional checking now...
				}
				else
					entityIter++;
			}
		}

		// Take a step in the world
		if (mWorld)
			mWorld->Step(dt, mPhysicsIterations);
	}
#pragma endregion

#pragma region Input and Event Handling
	// On Key Release
	void World::onKeyRelease(Window::RenderWindow* window, const sf::Event::KeyEvent& key)
	{
		// Handle key release
		switch (key.Code)
		{
			// Step forward in the slideshow
		case sf::Key::P:
			pushState(EB0::States::gPause);
			break;

		default:
			break;
		}
	}

	// On Lose Focus
	void World::onLoseFocus(Window::RenderWindow* window)
	{
		pushState(EB0::States::gPause);
	}
#pragma endregion

#pragma region Entity Management
	// Attach an entity to the world
	bool World::attachEntity(char* refname, Entities::Entity* entity)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Check if an eneity already with the given refname
		EntityMap::iterator entityIter;
		entityIter = mEntities.find(refname);

		if (entityIter != mEntities.end())
			return false;

		// Attach the entity
		mEntities[refname] = entity;

		if (mWorldLayer)
		{
			mWorldLayer->attachRenderable(entity);

			if (mWorldLayer->getWindow())
				mWorldLayer->getWindow()->attachFrameListener(entity);
		}

		if (entity)
			entity->onAttach(this);

		mDirtyEntityMap = true;

		return true;
	}

	// Fetch an entity with a given reference name
	Entities::Entity* World::fetchEntity(char* refname)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Check if an entity exists with the given refname
		EntityMap::iterator entityIter;
		entityIter = mEntities.find(refname);

		if (entityIter == mEntities.end())
			return 0; // no entity found

		// Entity found!
		return entityIter->second;
	}

	// Remove an entity with a given reference name
	bool World::removeEntity(char* refname)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Check if an entity exists with the given refname
		EntityMap::iterator entityIter;
		entityIter = mEntities.find(refname);

		if (entityIter != mEntities.end())
		{
			// Remove the entity
			if (mWorldLayer)
			{
				mWorldLayer->removeRenderable(entityIter->second);

				if (mWorldLayer->getWindow())
					mWorldLayer->getWindow()->removeFrameListener(entityIter->second);
			}

			if (entityIter->second)
				entityIter->second->onRemove(this);

			entityIter->second = 0;
			mEntities.erase(entityIter);

			mDirtyEntityMap = true;
			return true;
		}

		// No entity found to delete
		return false;
	}

	// Remove an entity with a given pointer
	bool World::removeEntity(Entities::Entity* entity)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Scan through each entity to determine if one exists with the given pointer
		EntityMap::iterator entityIter = mEntities.begin();
		bool found = false;

		while (entityIter != mEntities.end() && !found)
		{
			found = (entityIter->second == entity);
			if (!found) entityIter++;
		}

		// Did the entity exists? Let's delete it...
		if (found)
		{
			// Remove the entity
			if (mWorldLayer)
			{
				mWorldLayer->removeRenderable(entity);

				if (mWorldLayer->getWindow())
					mWorldLayer->getWindow()->removeFrameListener(entity);
			}

			if (entity)
				entity->onRemove(this);

			entityIter->second = 0;
			mEntities.erase(entityIter);

			mDirtyEntityMap = true;
		}
		
		// Return success
		return found;
	}

	// Remove all entites
	void World::removeAllEntities()
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Traverse each entity to destroy it
		EntityMap::iterator entityIter;

		for (entityIter = mEntities.begin(); entityIter != mEntities.end(); entityIter++)
		{
			// Remove the entity
			if (mWorldLayer)
			{
				mWorldLayer->removeRenderable(entityIter->second);

				if (mWorldLayer->getWindow())
					mWorldLayer->getWindow()->removeFrameListener(entityIter->second);
			}

			if (entityIter->second)
				entityIter->second->onRemove(this);

			entityIter->second = 0;
		}

		mEntities.clear();
		mDirtyEntityMap = true;
	}

	// Get number of entities
	int World::getNumEntities() const
	{
		return mEntities.size();
	}
#pragma endregion
} // namespace States
}